
/*-------------------------------------------------------------------------*/
/*	CodeDom builder for Yalgol compiler                                     */
/*	Y [20-04-05]                                                            */
/*-------------------------------------------------------------------------*/

using System;
using System.Collections;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace Yalgol
{
	public class CodeBuilder
	{
		public CodeBuilder()
		{
			// create compile unit
			code = new CodeCompileUnit();

			// create root namespace and add it to the compile unit
			currentNamespace = new CodeNamespace("");
			code.Namespaces.Add(currentNamespace);
		}

		// unit being compiled
		CodeCompileUnit code;
		public CodeCompileUnit Code
		{
			get { return code; }
		}
		
		// current namespace being compiled
		CodeNamespace currentNamespace;
		
		// namespace stack
		Stack namespaceStack = new Stack();

		// starts building new namespace
		public void BeginNamespace(string name)
		{
			// save current namespace
			namespaceStack.Push(currentNamespace);

			// get namespace name
			name = (currentNamespace.Name != "" ? currentNamespace.Name + "." : "") + name;

			// create new namespace
			currentNamespace = new CodeNamespace(name);

			// add this namespace to the current namespace
			code.Namespaces.Add(currentNamespace);
		}

		// ends building the current namespace, goes back to the parent
		public void EndNamespace()
		{
			// pop last namespace from stack
			currentNamespace = (CodeNamespace)namespaceStack.Pop();
		}

		// adds imports to the current namespace
		public void AddImports(ArrayList names)
		{
			foreach (string name in names)
	      {
				//Console.WriteLine(name);
				CodeNamespaceImport ns = new CodeNamespaceImport(name);
				currentNamespace.Imports.Add(ns);
			}
		}

		// current class
		CodeTypeDeclaration currentType = null;
		Stack typeStack = new Stack();

		// starts generating class
		public void BeginClass(string name)
		{
		   CodeTypeDeclaration ct = new CodeTypeDeclaration(name);
		   ct.IsClass = true;
		   ct.TypeAttributes = GetTypeAttributes(currentAccess);
//      	Console.WriteLine("Setting type {0} attributes to: {1}", ct.Name, ct.Attributes);

			if (currentType != null)
			{
				currentType.Members.Add(ct);
				typeStack.Push(currentType);
			}
			else
			{
				currentNamespace.Types.Add(ct);
			}

			currentType = ct;
		}

		public void EndClass()
		{
			if (typeStack.Count > 0)
				currentType = (CodeTypeDeclaration)typeStack.Pop();
			else
				currentType = null;
		}
		
		// starts generating interface
		public void BeginInterface(string name)
		{
		   CodeTypeDeclaration ct = new CodeTypeDeclaration(name);
		   ct.IsInterface = true;
		   ct.TypeAttributes = GetTypeAttributes(currentAccess);
//      	Console.WriteLine("Setting type {0} attributes to: {1}", ct.Name, ct.Attributes);

			if (currentType != null)
			{
				currentType.Members.Add(ct);
				typeStack.Push(currentType);
			}
			else
			{
				currentNamespace.Types.Add(ct);
			}

			currentType = ct;
		}

		public void EndInterface()
		{
			if (typeStack.Count > 0)
				currentType = (CodeTypeDeclaration)typeStack.Pop();
			else
				currentType = null;
		}
		
		// starts generating structure
		public void BeginStruct(string name)
		{
		   CodeTypeDeclaration ct = new CodeTypeDeclaration(name);
		   ct.IsStruct = true;
		   ct.TypeAttributes = GetTypeAttributes(currentAccess);
//      	Console.WriteLine("Setting type {0} attributes to: {1}", ct.Name, ct.Attributes);

			if (currentType != null)
			{
				currentType.Members.Add(ct);
				typeStack.Push(currentType);
			}
			else
			{
				currentNamespace.Types.Add(ct);
			}

			currentType = ct;
		}

		public void EndStruct()
		{
			if (typeStack.Count > 0)
				currentType = (CodeTypeDeclaration)typeStack.Pop();
			else
				currentType = null;
		}
		
		// starts generating enumeration
		public void BeginEnum(string name)
		{
		   CodeTypeDeclaration ct = new CodeTypeDeclaration(name);
		   ct.IsEnum = true;
		   ct.TypeAttributes = GetTypeAttributes(currentAccess);
//      	Console.WriteLine("Setting type {0} attributes to: {1}", ct.Name, ct.Attributes);

			if (currentType != null)
			{
				currentType.Members.Add(ct);
				typeStack.Push(currentType);
			}
			else
			{
				currentNamespace.Types.Add(ct);
			}

			currentType = ct;
		}

		public void EndEnum()
		{
			if (typeStack.Count > 0)
				currentType = (CodeTypeDeclaration)typeStack.Pop();
			else
				currentType = null;
		}

		// current member attributes
		AccessSpecifiers currentAccess = AccessSpecifiers.Private;
		Stack accessStack = new Stack();

		private MemberAttributes GetMemberAttributes(AccessSpecifiers access)
		{
			switch (access)
			{
				case AccessSpecifiers.Public:
					return MemberAttributes.Public;
				case AccessSpecifiers.Protected:
					return MemberAttributes.Family;
				case AccessSpecifiers.Internal:
					return MemberAttributes.Assembly;
				case AccessSpecifiers.ProtectedAndInternal:
					return MemberAttributes.FamilyAndAssembly;
				case AccessSpecifiers.ProtectedOrInternal:
					return MemberAttributes.FamilyOrAssembly;
				default:
					return MemberAttributes.Private;
			}
		}

		private TypeAttributes GetTypeAttributes(AccessSpecifiers access)
		{
			bool nested = typeStack.Count > 0;

			if (nested)
			{
				switch (access)
				{
					case AccessSpecifiers.Public:
						return TypeAttributes.NestedPublic;
					case AccessSpecifiers.Protected:
						return TypeAttributes.NestedFamily;
					case AccessSpecifiers.Internal:
						return TypeAttributes.NestedAssembly;
					case AccessSpecifiers.ProtectedAndInternal:
						return TypeAttributes.NestedFamANDAssem;
					case AccessSpecifiers.ProtectedOrInternal:
						return TypeAttributes.NestedFamORAssem;
					default:
						return TypeAttributes.NestedPrivate;
				}
			}
			else
			{
				switch (access)
				{
					case AccessSpecifiers.Public:
						return TypeAttributes.Public;
					default:
						return TypeAttributes.NotPublic;
				}
			}
		}

      // sets current access specifier
      public void SetAccess(AccessSpecifiers access)
      {
      	currentAccess = access;
//      	Console.WriteLine("Setting current attributes to: {0}", currentAccess);
      }

      // saves current access specifier
      public void PushAccess()
      {
      	accessStack.Push(currentAccess);
      }
		
      // saves current access specifier
      public void PopAccess()
      {
      	currentAccess = (AccessSpecifiers)accessStack.Pop();
      }
		
		// add enum member (with possible value)
		public void AddEnumMember(string name, string value)
		{
			CodeMemberField member = new CodeMemberField(typeof(int), name);

			// initial value not supported by CodeDom, so have to use UserData
			if (value != null && value != "")
				member.UserData["Value"] = value;

			currentType.Members.Add(member);
		}

		// adds field to the current type
		public void AddField(string name, CodeTypeReference type)
		{
			CodeMemberField cf = new CodeMemberField();
			cf.Name = name;
			cf.Type = type;
			cf.Attributes = GetMemberAttributes(currentAccess);

			currentType.Members.Add(cf);
		}

		// adds property to the current type
		public void AddProperty(string name, CodeTypeReference type, CodeStatement getStatement, CodeStatement setStatement)
		{
			CodeMemberProperty cp = new CodeMemberProperty();
			cp.Name = name;
			cp.GetStatements.Add(getStatement);
			cp.SetStatements.Add(setStatement);
			cp.Type = type;
			cp.Attributes = GetMemberAttributes(currentAccess);

			currentType.Members.Add(cp);
		}

		// adds method to the current type
		public void AddMethod(string name, CodeTypeReference retType, ArrayList args, ArrayList stmts)
		{
			CodeMemberMethod cm;
			
			if (name == ".ctor")
				cm = new CodeConstructor();
			else
			{
				cm = new CodeMemberMethod();
				cm.Name = name;
			}
			
			cm.Attributes = GetMemberAttributes(currentAccess);

			if (retType != null)
				cm.ReturnType = retType;

			if (args != null && args.Count > 0)
				cm.Parameters.AddRange(
					(CodeParameterDeclarationExpression[])
						args.ToArray(typeof(CodeParameterDeclarationExpression)));

			if (stmts != null && stmts.Count > 0)
/*			{
				Array s = stmts.ToArray(typeof(CodeStatement));
				foreach (object o in s)
					Console.WriteLine("stmt = {0}", o);
			} // */
//*
				cm.Statements.AddRange(
					(CodeStatement[])
						stmts.ToArray(typeof(CodeStatement))); //*/

			currentType.Members.Add(cm);
		}

		// creates primitive expression (for literal value)
		public CodeExpression CreatePrimitiveExpression(object value)
		{
			return new CodePrimitiveExpression(value);
		}
		
		// creates "this" expression
		public CodeExpression CreateThisReferenceExpression()
		{
			return new CodeThisReferenceExpression();
		}

		// creates "super" expression ("base" in C#)
		public CodeExpression CreateSuperReferenceExpression()
		{
			return new CodeBaseReferenceExpression();
		}

		// creates "new type(args)" expression
		public CodeExpression CreateNewExpression(CodeTypeReference type, ArrayList args)
		{
			if (args != null && args.Count > 0)
			{
				foreach (object o in args)
					Console.WriteLine(o);

				return new CodeObjectCreateExpression(type); /*, 
					(CodeExpression[])args.ToArray(typeof(CodeExpression)));*/
			}
			else
				return new CodeObjectCreateExpression(type);
		}
	}

	public enum AccessSpecifiers
	{
		None = 0, Private, Public, Protected, 
		Internal, ProtectedAndInternal, ProtectedOrInternal
	}
}
